﻿Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Math
Partial Public Class TextBoxEx
#Region "剪贴板操作"
    ''' <summary>
    ''' 剪切
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Cut()
        Dim strSelection As String
        strSelection = ExDocument.GetSelectionText
        If strSelection IsNot String.Empty Then
            My.Computer.Clipboard.Clear()
            Clipboard.SetDataObject(strSelection)
            Delete()
            ExDocument.ClearSelection()
            If ExDocument.ResetMaxLineWidth() Then RaiseEvent ChangeHScroll()
            If mInputMode = EnumInputMode.OverwriteChar Then mCaret.ChangeWidth(GetCaretWidth)
            ResetCaretPos()
            MakeCaretVisible()
            mCaret.SetCaretPos(CaretPosition)
        End If
    End Sub
    ''' <summary>
    ''' 复制
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Copy()
        'SetText的参数不能为空
        Dim strSelection As String
        strSelection = ExDocument.GetSelectionText
        If strSelection IsNot String.Empty Then
            My.Computer.Clipboard.Clear()
            Clipboard.SetDataObject(strSelection)
            ResetCaretPos()
            MakeCaretVisible()
            mCaret.SetCaretPos(CaretPosition)
        End If
    End Sub
    ''' <summary>
    ''' 粘贴
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Paste()
        If My.Computer.Clipboard.ContainsText Then
            Dim strSelection As String
            strSelection = My.Computer.Clipboard.GetText
            If ExDocument.HasSelection Then
                ExDocument.UpdateSelectionText(strSelection)
            Else
                ExDocument.InsertSelection(strSelection)
            End If
            If ExDocument.ResetMaxLineWidth() Then RaiseEvent ChangeHScroll()
            If mInputMode = EnumInputMode.OverwriteChar Then mCaret.ChangeWidth(GetCaretWidth)
            ResetCaretPos()
            MakeCaretVisible()
            mCaret.SetCaretPos(CaretPosition)
        End If
    End Sub
    ''' <summary>
    ''' 删除
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Delete()
        Dim strSelection As String
        strSelection = ExDocument.GetSelectionText
        If strSelection IsNot String.Empty Then
            If ExDocument.CurrentSelectType = Document.SelectType.Normal Then
                '普通的删除
                ExDocument.DeleteSelection()
            Else
                '箱型的删除
                ExDocument.DeleteBoxSelection()
            End If
            ExDocument.ClearSelection()
            If ExDocument.ResetMaxLineWidth() Then RaiseEvent ChangeHScroll()
            If mInputMode = EnumInputMode.OverwriteChar Then mCaret.ChangeWidth(GetCaretWidth)
            ResetCaretPos()
            MakeCaretVisible()
            mCaret.SetCaretPos(CaretPosition)
        End If
    End Sub
    ''' <summary>
    ''' 撤销
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Undo()
        ExDocument.StepList.Undo()
        ResetCaretPos()
        MakeCaretVisible()
        mCaret.SetCaretPos(CaretPosition)
        If ExDocument.ResetMaxLineWidth() Then RaiseEvent ChangeHScroll()
        If mInputMode = EnumInputMode.OverwriteChar Then mCaret.ChangeWidth(GetCaretWidth)
        ResetCaretPos()
        MakeCaretVisible()
        mCaret.SetCaretPos(CaretPosition)
    End Sub
    ''' <summary>
    ''' 重做
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Redo()
        ExDocument.StepList.Redo()
        If ExDocument.ResetMaxLineWidth() Then RaiseEvent ChangeHScroll()
        If mInputMode = EnumInputMode.OverwriteChar Then mCaret.ChangeWidth(GetCaretWidth)
        ResetCaretPos()
        MakeCaretVisible()
        mCaret.SetCaretPos(CaretPosition)
    End Sub
#End Region
#Region "查找和替换"
    ''' <summary>
    ''' 跳转到某行
    ''' </summary>
    ''' <param name="RealLineNumber">真实行号</param>
    ''' <param name="vPos">垂直位置</param>
    ''' <remarks></remarks>
    Public Sub JumpToLine(ByVal RealLineNumber As Integer, ByVal vPos As VPosition)
        If RealLineNumber < 1 Then RealLineNumber = 1
        If RealLineNumber > ExDocument.MaxRealLineNumber Then RealLineNumber = ExDocument.MaxRealLineNumber
        '如果该行是不可见的话，先强制将这个代码块变为可见。
        ExDocument.MakeLineVisible(RealLineNumber)
        Select Case vPos
            Case VPosition.Top
                FirstVisiableLineNumber = ExDocument.GetDisplayLineByRealLine(RealLineNumber)
            Case VPosition.Middle
                FirstVisiableLineNumber = Max(1, ExDocument.GetDisplayLineByRealLine(RealLineNumber) - VisibleLines / 2)
            Case VPosition.Bottom
                FirstVisiableLineNumber = Max(1, ExDocument.GetDisplayLineByRealLine(RealLineNumber) - VisibleLines + 1)
        End Select
        FirstLineChanged()
    End Sub
    ''' <summary>
    ''' 简单替换
    ''' </summary>
    ''' <param name="newString"></param>
    ''' <remarks></remarks>
    Public Sub Replace(ByVal newString As String)
        ExDocument.UpdateSelectionText(newString)
        Me.Refresh()
        FindNext()
    End Sub
    ''' <summary>
    ''' 用指定字符替换所有字符
    ''' </summary>
    ''' <param name="oldstring"></param>
    ''' <param name="newString"></param>
    ''' <remarks></remarks>
    Public Sub ReplaceAll(ByVal oldstring As String, ByVal newString As String)
        ExDocument.ReplaceAll(oldstring, newString)
        Me.Refresh()
    End Sub
    Public Sub Find(ByVal Key As String, ByVal Directory As Boolean)
        If ExDocument.Find(Key, Directory) Then
            JumpToLine(ExDocument.InputPosition.Y, VPosition.Middle)
        Else
            RaiseEvent WordNotFound()
        End If
    End Sub
    Public Sub FindNext()
        If ExDocument.HasSelection Then
            If ExDocument.GetSelectionText = ExDocument.SearchKeyWord Then
                ExDocument.InputPosition = ExDocument.SelectEndRealPos
            End If
        End If
        If ExDocument.FindNext Then
            JumpToLine(ExDocument.InputPosition.Y, VPosition.Middle)
        Else
            RaiseEvent WordNotFound()
        End If
    End Sub
    Public Sub FindPrevious()
        If ExDocument.HasSelection Then
            If ExDocument.GetSelectionText = ExDocument.SearchKeyWord Then
                ExDocument.InputPosition = ExDocument.SelectStartRealPos
            End If
        End If
        If ExDocument.FindPre Then
            JumpToLine(ExDocument.InputPosition.Y, VPosition.Middle)
        Else
            RaiseEvent WordNotFound()
        End If
    End Sub
#End Region
#Region "文字绘制"
    '''' <summary>
    '''' 绘制指定行为中心的3行
    '''' </summary>
    '''' <param name="LineNumber">中间行行号</param>
    '''' <remarks></remarks>
    Sub DrawLinesByCenterLineNumber(ByVal DisplayNumber As Integer)
        Dim orgVisible As Boolean = mCaret.IsCaretVisible
        mCaret.HideCaret()
        If (DisplayNumber - 1) >= FirstVisiableLineNumber Then DrawLine(DisplayNumber - 1)
        DrawLine(DisplayNumber)
        If Not (DisplayNumber + 1 > Math.Min(CInt(FirstVisiableLineNumber + VisibleLines - 1), CInt(ExDocument.MaxDisplayLineNumber))) Then
            DrawLine(DisplayNumber + 1)
        End If
        If orgVisible Then mCaret.ShowCaret()
    End Sub
    ''' <summary>
    ''' 行绘制
    ''' </summary>
    ''' <param name="DisplayLineNumber">显示行号（从1开始）</param>
    ''' <remarks></remarks>
    Sub DrawLine(ByVal DisplayLineNumber As Integer, Optional ByVal g As Graphics = Nothing)
        '这里一定要重新获取Graphics对象，不然的话，可以绘图的区域将是Graphics实例化时的控件尺寸。
        If (DisplayLineNumber < FirstVisiableLineNumber) Or (DisplayLineNumber > VisibleLines + FirstVisiableLineNumber) Then
            Exit Sub
        End If
        If g Is Nothing Then g = Me.CreateGraphics
        Dim ProcessLine As Line
        ProcessLine = ExDocument.GetMemoryLineByDisplayLineNumber(DisplayLineNumber)
        Dim strText As String
        strText = ProcessLine.Text
        '擦去原来的所有可见内容
        Dim strRec As RectangleF = New RectangleF(0, ExFont.Height * (DisplayLineNumber - FirstVisiableLineNumber), _
                                CType(Me.ClientRectangle.Width, Single), ExFont.Height)
        g.FillRectangle(New SolidBrush(ExBackColor), strRec)
        Dim strSelection As String
        Dim strBeforeSelection As String
        Dim BeforeSelectionWidth As Integer
        Dim SelectionWidth As Integer
        Dim SelectionRec As RectangleF
        Dim DrawStringFormat As StringFormat
        DrawStringFormat = StringFormat.GenericTypographic

        If ProcessLine.IsWholeSelected = False Then
            If (ExDocument.GetMemoryLineByDisplayLineNumber(DisplayLineNumber).Real_IsCommentLine) Or _
                ExDocument.GetMemoryLineByDisplayLineNumber(DisplayLineNumber).Dis_IsPartyOfComment Then
                '绘制字符
                If strText <> String.Empty Then
                    g.DrawString(strText, ExFont, New SolidBrush(ExCommentColor), -offset, (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, DrawStringFormat)
                End If
            Else
                '绘制字符
                If strText <> String.Empty Then
                    g.DrawString(strText, ExFont, New SolidBrush(ExForeColor), -offset, (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, DrawStringFormat)
                End If
                '上色
                Dim KeyColor As Color
                Dim TestWord As String
                With ProcessLine
                    If .WordList.Count > 0 Then
                        For KeyCount As Integer = 0 To .WordList.Count - 1
                            KeyColor = ExForeColor
                            TestWord = Mid(strText, .WordList(KeyCount).StartPos, .WordList(KeyCount).Length)
                            If ColorfulWord.ContainsKey(UCase(TestWord)) Then
                                KeyColor = ColorfulWord(UCase(TestWord))
                            End If
                            If KeyColor <> ExForeColor Then
                                g.DrawString(Mid(strText, .WordList(KeyCount).StartPos, .WordList(KeyCount).Length), ExFont, _
                                New SolidBrush(KeyColor), MeasureSingleTextWidth(Mid(strText, 1, .WordList(KeyCount).StartPos - 1)) - offset, _
                                (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, DrawStringFormat)
                            End If
                        Next
                    End If
                End With
                '引用部分的单词
                Dim LeftChar As Char
                Dim RightChar As Char
                Dim StartLeftpos As Integer
                Dim StartRightpos As Integer

                Dim MatchDic As New Dictionary(Of Char, Char)
                MatchDic.Add(Chr(34), Chr(34))
                MatchDic.Add(Chr(39), Chr(39))
                MatchDic.Add("<", ">")
                MatchDic.Add("(", ")")

                For Each LC As Char In MatchDic.Keys
                    LeftChar = LC
                    RightChar = MatchDic(LC)
                    StartLeftpos = 1
                    StartRightpos = 1
                    StartLeftpos = InStr(StartLeftpos, strText, LeftChar)
                    Do While StartLeftpos > 0
                        StartRightpos = StartLeftpos + 1
                        If StartRightpos >= strText.Length Then Exit Do
                        StartRightpos = InStr(StartRightpos, strText, RightChar)
                        If StartRightpos > 0 Then
                            g.DrawString(Mid(strText, StartLeftpos + 1, StartRightpos - StartLeftpos - 1), ExFont, _
                            New SolidBrush(ExQuateColor), MeasureSingleTextWidth(Mid(strText, 1, StartLeftpos)) - offset, _
                            (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, DrawStringFormat)
                            StartLeftpos = StartRightpos + 1
                            If StartLeftpos >= strText.Length Then Exit Do
                            StartLeftpos = InStr(StartLeftpos, strText, LeftChar)
                        Else
                            Exit Do
                        End If
                    Loop
                Next
                '注释内容
                If SingleCommentMark <> String.Empty Then
                    If InStr(strText, SingleCommentMark) > 0 Then
                        '绘制当前注释部分
                        g.DrawString(Mid(strText, InStr(strText, SingleCommentMark)), ExFont, _
                        New SolidBrush(ExCommentColor), MeasureSingleTextWidth(Mid(strText, 1, InStr(strText, SingleCommentMark) - 1)) - offset, _
                        (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, DrawStringFormat)
                        'TODO 当折行显示的时候，还必须告诉下面的显示行信息，下面这些表示行也是被注释的
                        If ExDocument.GetMemoryLineByDisplayLineNumber(DisplayLineNumber).Dis_IsLastLine = False Then
                            For i As Integer = DisplayLineNumber + 1 To ExDocument.MaxDisplayLineNumber
                                ExDocument.GetMemoryLineByDisplayLineNumber(i).Dis_IsPartyOfComment = True
                                If ExDocument.GetMemoryLineByDisplayLineNumber(i).Dis_IsLastLine Then Exit For
                            Next
                        End If
                    End If
                End If
            End If
        End If
        '如果当前行有选中的内容
        If ProcessLine.HasSelection Then
            With ProcessLine
                If .SelectLength > 0 And .HasSelection Then
                    strBeforeSelection = Mid(strText, 1, .SelectStart - 1)
                    strSelection = Mid(strText, .SelectStart, .SelectLength)
                    BeforeSelectionWidth = MeasureSingleTextWidth(strBeforeSelection)
                    SelectionWidth = MeasureSingleTextWidth(strSelection)
                    SelectionRec = New RectangleF(BeforeSelectionWidth - offset, ExFont.Height * (DisplayLineNumber - FirstVisiableLineNumber), SelectionWidth, ExFont.Height)
                    g.FillRectangle(New SolidBrush(ExSelectionBackColor), SelectionRec)
                    g.DrawString(strSelection, ExFont, New SolidBrush(ExSelectionForeColor), BeforeSelectionWidth - offset, (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, DrawStringFormat)
                End If
            End With
        End If
        '绘制检索词
        If ProcessLine.HasHeightLightWord Then
            With ProcessLine
                If .WordList.Count > 0 Then
                    For KeyCount As Byte = 0 To .WordList.Count - 1
                        If UCase(strKeyWord) = UCase(Mid(strText, .WordList(KeyCount).StartPos, .WordList(KeyCount).Length)) Then
                            strBeforeSelection = Mid(strText, 1, .WordList(KeyCount).StartPos - 1)
                            BeforeSelectionWidth = MeasureSingleTextWidth(strBeforeSelection)
                            SelectionWidth = MeasureSingleTextWidth(strKeyWord)
                            SelectionRec = New RectangleF(BeforeSelectionWidth - offset, ExFont.Height * (DisplayLineNumber - FirstVisiableLineNumber), SelectionWidth, ExFont.Height)
                            g.FillRectangle(New SolidBrush(Color.Gold), SelectionRec)
                            g.DrawString(strKeyWord, ExFont, New SolidBrush(Color.Blue), MeasureSingleTextWidth(Mid(strText, 1, .WordList(KeyCount).StartPos - 1)) - offset, _
                            (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, DrawStringFormat)
                        End If
                    Next
                End If
            End With
        End If
        '列模式的时候，插入位置
        If ExColumnMode And ExDocument.ColumnModeList IsNot Nothing Then
            If ExDocument.ColumnModeList.ContainsKey(ProcessLine.Dis_RealLineNumber) Then
                g.DrawLine(New Pen(Color.Red, 2), _
                 MeasureSingleTextWidth(Mid(strText, 1, ExDocument.ColumnModeList(ProcessLine.Dis_RealLineNumber) - 1)), _
                 ExFont.Height * (DisplayLineNumber - FirstVisiableLineNumber), _
                 MeasureSingleTextWidth(Mid(strText, 1, ExDocument.ColumnModeList(ProcessLine.Dis_RealLineNumber) - 1)), _
                 ExFont.Height * (DisplayLineNumber - FirstVisiableLineNumber + 1))
            End If
        End If
        '如果是折叠着的首行
        If ProcessLine.Real_IsBlockHeader And ProcessLine.Real_IsFolder Then
            g.DrawRectangle(Pens.Pink, -offset, (DisplayLineNumber - FirstVisiableLineNumber) * ExFont.Height, ProcessLine.Width, ExFont.Height - 1)
        End If
        '代码左右限制线
        DrawlimitLine(g)
    End Sub
    ''' <summary>
    ''' 双缓冲用图片对象
    ''' </summary>
    ''' <remarks></remarks>
    Dim BitMapBuffer As Bitmap
    ''' <summary>
    ''' 重新绘制所有行
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub ReDrawBuffer()
        If Not ShareG Is Nothing Then ResetCaretPos()
        mCaret.SetCaretPos(CaretPosition)
        BitMapBuffer = New Bitmap(Me.Width, Me.Height)
        Dim g As Graphics = Graphics.FromImage(BitMapBuffer)
        g.Clear(ExBackColor)
        For intLines As Integer = FirstVisiableLineNumber To Math.Min(CInt(FirstVisiableLineNumber + VisibleLines - 1), CInt(ExDocument.MaxDisplayLineNumber))
            DrawLine(intLines, g)
        Next
        DrawlimitLine(g)
    End Sub
    '重新绘制
    Private Sub RePaintAllLines(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
        Dim orgVisible As Boolean = mCaret.IsCaretVisible
        Debug.Print("Hide RePaintAllLines")
        mCaret.HideCaret()
        ReDrawBuffer()
        e.Graphics.DrawImage(BitMapBuffer, 0, 0)
        If orgVisible Then mCaret.ShowCaret()
    End Sub
    ''' <summary>
    ''' 绘制代码限制长度标尺线
    ''' </summary>
    ''' <param name="g"></param>
    ''' <remarks></remarks>
    Sub DrawlimitLine(ByVal g As Graphics)
        If mSingleLineMaxLength > 0 Then g.DrawLine(New Pen(ExRulerColor, 1), MeasureTextWidth(Space(mSingleLineMaxLength)) + 2, 0, MeasureTextWidth(Space(mSingleLineMaxLength)) + 2, Me.Height)
        If mSingleLineMinLength > 0 Then g.DrawLine(New Pen(ExRulerColor, 1), MeasureTextWidth(Space(mSingleLineMinLength)) - 2, 0, MeasureTextWidth(Space(mSingleLineMinLength)) - 2, Me.Height)
    End Sub
#End Region
#Region "整行选择"
    ''' <summary>
    ''' 使用行号栏做选择操作
    ''' </summary>
    ''' <param name="StartLineNumber"></param>
    ''' <param name="EndLineNumber"></param>
    ''' <param name="FirstLine"></param>
    ''' <remarks></remarks>
    Sub SelectWholeLines(ByVal StartLineNumber As Integer, ByVal EndLineNumber As Integer, ByVal FirstLine As Integer)
        Dim TStartLineNumber As Integer = Min(StartLineNumber, EndLineNumber)
        Dim TEndLineNumber As Integer = Max(StartLineNumber, EndLineNumber)
        With ExDocument
            .ClearSelection()
            If TStartLineNumber = 0 Then
                If TEndLineNumber = 1 Then
                    Me.Refresh()
                    Exit Sub
                Else
                    TStartLineNumber = 1
                End If
            End If
            '设定插入位置
            .InputPosition = New Point(ExDocument.GetRealLineText(TEndLineNumber).Length + 1, TEndLineNumber)
            .DisplayPosition = .GetDisplayPosByInputPos(.InputPosition)
            .UpdateSelection(New Point(1, TStartLineNumber))
            ResetCaretPos()
            mCaret.SetCaretPos(CaretPosition)
            mCaret.ShowCaret()
            If FirstLine <> FirstVisiableLineNumber Then
                FirstVisiableLineNumber = FirstLine
                FirstLineChanged()
            End If
            Me.Refresh()
        End With
    End Sub
#End Region
End Class
